﻿//------------------------------------------------------------------------------
// <copyright file="CommerceServerMetadataProcessor.cs" company="CommerceServer.net, Inc">
//    (c) 2012 CommerceServer.net, Inc. and its affiliates. All rights reserved.
// </copyright>
// <summary></summary>
//------------------------------------------------------------------------------ 
namespace CommerceServerContrib.CodeGeneration
{
    using CommerceServer.Foundation;
    using CommerceServer.Foundation.Definitions;
    using CommerceServerContrib.CodeGeneration.Configuration;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Globalization;
    using System.Linq;

    public class CommerceServerMetadataProcessor : Foundation
    {
        private CommerceServerCodeGenerationConfigurationSection configuration = (CommerceServerCodeGenerationConfigurationSection)ConfigurationManager.GetSection("commerceServerCodeGeneration");

        public CommerceServerMetadataProcessor()
            : base()
        {
        }

        public CommerceServerMetadataProcessor(string channel, string userLocale, string userUILocale) :
            base(channel, userLocale, userUILocale)
        {
        }

        // TODO:  Break this up or otherwise make it easier to override
        public List<ClassDefinition> ClassDefinitions()
        {
            var metadataQuery = new CommerceQuery<CommerceEntityDefinition, CommerceModelSearch<CommerceEntity>>();
            var entityMappingsQuery = new CommerceQueryRelatedItem<EntityMapping>(CommerceEntityDefinition.RelationshipName.EntityMappings);
            metadataQuery.RelatedOperations.Add(entityMappingsQuery);

            var definitionMappingsQuery = new CommerceQueryRelatedItem<DefinitionMapping>(EntityMapping.RelationshipName.DefinitionMappings);
            entityMappingsQuery.RelatedOperations.Add(definitionMappingsQuery);

            var response = this.InvokeFoundation(metadataQuery.ToRequest());

            var queryResponse = response.OperationResponses[0] as CommerceQueryOperationResponse;
            var entityDefinitions = queryResponse.CommerceEntities.ConvertAll<CommerceEntityDefinition>(bid => bid as CommerceEntityDefinition);
            var classDefinitions = new List<ClassDefinition>();
            foreach (CommerceEntityDefinition entityDefinition in entityDefinitions)
            {
                if (this.ShouldProcessEntity(entityDefinition.Name))
                {
                    var areas = this.GetAreasFromEntityDefinition(entityDefinition);
                    foreach (CommerceServerAreaContainer areaContainer in areas)
                    {
                        var completeDefinition = this.GetFullDefinition(areaContainer.Area, entityDefinition.Name);
                        var classDefinition = BuildClassDefinition(areaContainer.Area, completeDefinition);

                        classDefinitions.Add(classDefinition);
                    }
                }
            }

            return classDefinitions;
        }

        public ClassDefinition BuildClassDefinition(
            CommerceServerArea? area,
            CommerceEntityDefinition completeDefinition)
        {
            var entityMappings = new List<EntityMapping>(completeDefinition.EntityMappings.Select(c => c.Target as EntityMapping));

            var classDefinition = new ClassDefinition(Utilities.CleanName(completeDefinition.Name), area);
            classDefinition.WeaklyTypedName = completeDefinition.Name;

            this.CleanupRelationships(completeDefinition);

            foreach (var propertyDefinition in completeDefinition.PropertyDefinitions)
            {
                var simplified = new SimplifiedPropertyDefinition(propertyDefinition.Target as PropertyDefinition);
                classDefinition.Properties.Add(simplified);
            }

            foreach (EntityMapping entityMapping in entityMappings)
            {
                if (entityMapping.DefinitionMappings != null && entityMapping.DefinitionMappings.Count > 0)
                {
                    foreach (var definitionMapping in entityMapping.DefinitionMappings)
                    {
                        // TODO:  At some point this should deal with definition specific extensions.
                        if (definitionMapping.Target.Properties.Contains("PropertyMappings"))
                        {
                            var propertyMappings = definitionMapping.Target.Properties["PropertyMappings"] as StringPairCollection;
                            if (propertyMappings != null && propertyMappings.Count > 0)
                            {
                                foreach (var mapping in propertyMappings)
                                {
                                    // find by value
                                    var currentSimplifiedPropertyDefinition = this.GetSimplifiedPropertyDefinitionForMapping(mapping, classDefinition);
                                    if (currentSimplifiedPropertyDefinition != null)
                                    {
                                        // Definition specific rewrite.
                                    }
                                    else
                                    {
                                        // Not sure what the return value should be
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (completeDefinition.RelationshipDefinitions != null)
            {
                this.AddRelationshipPropertiesToClass(classDefinition, completeDefinition);
            }

            return classDefinition;
        }

        public virtual CommerceEntityDefinition GetFullDefinition(CommerceServerArea? areaContainer, string modelName)
        {
            var propertyDefinitions = new CommerceQueryRelatedItem<PropertyDefinition>("PropertyDefinitions")
            {
                RelatedOperations = { new CommerceQueryRelatedItem<ConstraintBase>("Constraints"), new CommerceQueryRelatedItem<EnumerationEntry>("EnumeratedValues") }
            };

            var relationshipType = new CommerceQueryRelatedItem<RelationshipTypeDefinition>("RelationshipType")
            {
                RelatedOperations = { propertyDefinitions }
            };

            CommerceQueryRelatedItem<RelationshipDefinition> relationshipDefinition = new CommerceQueryRelatedItem<RelationshipDefinition>("RelationshipDefinitions")
            {
                RelatedOperations = { relationshipType }
            };

            var entityPropertyDefinition = new CommerceQueryRelatedItem<PropertyDefinition>("PropertyDefinitions")
            {
                RelatedOperations = { new CommerceQueryRelatedItem<ConstraintBase>("Constraints"), new CommerceQueryRelatedItem<EnumerationEntry>("EnumeratedValues") }
            };

            var entityMetadata = new CommerceQueryRelatedItem<PropertyDefinition>("EntityMetadata")
            {
                RelatedOperations = { new CommerceQueryRelatedItem<ConstraintBase>("Constraints"), new CommerceQueryRelatedItem<EnumerationEntry>("EnumeratedValues") }
            };

            var entityMappings = new CommerceQueryRelatedItem<EntityMapping>("EntityMappings")
            {
                RelatedOperations = { new CommerceQueryRelatedItem<DefinitionMapping>("DefinitionMappings") }
            };

            entityMappings.Model.Properties.Add("PropertyMappings");

            if (areaContainer != null)
            {
                entityMappings.SearchCriteria.Model.CommerceServerArea = areaContainer;
            }

            var query = new CommerceQuery<CommerceEntityDefinition, CommerceModelSearch<CommerceEntity>>();
            query.SearchCriteria.Model.ModelName = modelName;
            query.RelatedOperations.Add(entityMappings);
            query.RelatedOperations.Add(entityPropertyDefinition);
            query.RelatedOperations.Add(relationshipDefinition);
            query.RelatedOperations.Add(entityMetadata);

            var response = this.InvokeFoundation(query.ToRequest());
            var queryOperationResponse = response.OperationResponses[0] as CommerceQueryOperationResponse;
            if (queryOperationResponse != null && queryOperationResponse.Count > 0)
            {
                return queryOperationResponse.CommerceEntities[0] as CommerceEntityDefinition;
            }

            return null;
        }

        protected void CleanupRelationships(CommerceEntityDefinition completeDefinition)
        {
            foreach (var relationship in completeDefinition.RelationshipDefinitions)
            {
                var item = completeDefinition.PropertyDefinitions.Where(c => c.Target.Properties["Name"].ToString().Equals(relationship.Target.Properties["Name"].ToString())).SingleOrDefault();
                completeDefinition.PropertyDefinitions.Remove(item);
            }
        }

        /// <summary>
        /// Determines if the entity should be processed.  
        /// </summary>
        /// <param name="entityName">Name of the entity.</param>
        /// <returns>True if code should be generated for the entity; false otherwise. </returns>
        protected virtual bool ShouldProcessEntity(string entityName)
        {
            if (configuration.EntitiesToGenerate != null &&
                configuration.EntitiesToGenerate.Count > 0)
            {
                if (configuration.EntitiesToGenerate.Contains(entityName) &&
                !configuration.EntitiesToIgnore.Contains(entityName))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            return !configuration.EntitiesToIgnore.Contains(entityName);
        }

        protected virtual void AddRelationshipPropertiesToClass(ClassDefinition classDefinition, CommerceEntityDefinition fullEntityDefinition)
        {
            foreach (CommerceRelationship relationship in fullEntityDefinition.RelationshipDefinitions)
            {
                var mapping = relationship.Target as RelationshipDefinition;

                if (mapping != null)
                {
                    var propertyDefinition = new SimplifiedRelationshipDefinition();

                    propertyDefinition.Name = Utilities.CleanName(mapping.Name);
                    propertyDefinition.IsMultipleValued = mapping.IsMultipleItems.Value;
                    propertyDefinition.WeaklyTypedName = mapping.Name;
                    propertyDefinition.AssociatedEntity = Utilities.CleanName(mapping.TargetCommerceEntity);

                    if (mapping.CommerceArea != null && mapping.CommerceArea.HasValue && propertyDefinition.AssociatedEntity != "any")
                    {
                        propertyDefinition.AssociatedEntity += mapping.CommerceArea.Value;
                    }

                    if (propertyDefinition.AssociatedEntity == "any")
                    {
                        propertyDefinition.AssociatedEntity = "object";
                    }

                    classDefinition.Relationships.Add(propertyDefinition);
                }
            }
        }

        private SimplifiedPropertyDefinition GetSimplifiedPropertyDefinitionForMapping(StringPair mapping, ClassDefinition classDefinition)
        {
            return classDefinition.Properties.Where(p => p.Name == mapping.Value).SingleOrDefault();
        }

        private List<CommerceServerAreaContainer> GetAreasFromEntityDefinition(CommerceEntityDefinition entityDefinition)
        {
            var areas = new List<CommerceServerAreaContainer>();

            if (entityDefinition.EntityMappings != null && entityDefinition.EntityMappings.Count > 0)
            {
                foreach (CommerceRelationship relationship in entityDefinition.EntityMappings)
                {
                    var area = ((EntityMapping)relationship.Target).CommerceServerArea;

                    // check and see if there is actually any area associated to this
                    if (area == null || !area.HasValue)
                    {
                        continue;
                    }

                    switch (area.Value.ToString().ToLower(CultureInfo.InvariantCulture))
                    {
                        case "catalog":
                            areas.Add(new CommerceServerAreaContainer(CommerceServerArea.Catalog));
                            break;
                        case "orders":
                            areas.Add(new CommerceServerAreaContainer(CommerceServerArea.Orders));
                            break;
                        case "profiles":
                            areas.Add(new CommerceServerAreaContainer(CommerceServerArea.Profiles));
                            break;
                        case "marketing":
                            areas.Add(new CommerceServerAreaContainer(CommerceServerArea.Marketing));
                            break;
                    }
                }
            }

            // even nothing is considered an area so add in null if none is present
            if (areas.Count <= 1)
            {
                areas.Clear();
                areas.Add(new CommerceServerAreaContainer());
            }

            return areas;
        }
    }
}
